#include "inccommon.h"
#include "gui.h"

#define	FREEGLUT_LIB_PRAGMAS	0
#define	FREEGLUT_STATIC			1
#include "glut.h"

#include "tracer/cuda_tracer.h"
#include "scene/scene.h"
#include "scene/objects.h"
#include "utility/image.h"

using rtMath::vec2;
using rtMath::vec3;



GUI& GUI::Instance(void)
{
	static GUI _guiInstance;
	return _guiInstance;
}

// glut callbacks
void GUI::DisplayCallback(void)
{
	GUI::Instance().Display();
}
void GUI::ResizeCallback(int width, int height)
{
	GUI::Instance().Resize(width, height);
}
void GUI::KeyboardCallback(uint8 key, int x, int y)
{
	GUI::Instance().Keyboard(key, x, y);
}
void GUI::MouseCallback(int button, int state, int x, int y)
{
	GUI::Instance().Mouse(button, state, x, y);
}
void GUI::MotionCallback(int x, int y)
{
	GUI::Instance().Motion(x, y);
}


GUI::GUI()
	: m_Screen(NULL)
	, m_Scene(NULL)
	, m_Tracer(NULL)
	, m_WindowWidth(0)
	, m_WindowHeight(0)
	, m_TextureWidth(0)
	, m_TextureHeight(0)
	, m_GLTexture(0)
{
}
GUI::~GUI()
{
	SafeDelete(m_Tracer);
	SafeDelete(m_Screen);
}



bool GUI::Create(int wndWidth, int wndHeight, const rtString& caption)
{
	m_WindowWidth = wndWidth;
	m_WindowHeight = wndHeight;

	m_Screen = Image::CreateFromMemory(NULL, wndWidth, wndHeight, 32);
	if (!m_Screen)
		return false;

	char *argv[] = {"foo", "bar"};
	int argc = 2;
	glutInit(&argc, argv);

	glutInitWindowSize(wndWidth, wndHeight);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
	glutCreateWindow(caption.c_str());

	glutDisplayFunc(GUI::DisplayCallback);
	glutReshapeFunc(GUI::ResizeCallback);
	glutKeyboardFunc(GUI::KeyboardCallback);
	glutMouseFunc(GUI::MouseCallback);
	glutMotionFunc(GUI::MotionCallback);

	this->RecreateTexture();

	return true;
}

int GUI::Run(Scene& scene)
{
	m_Scene = &scene;
	m_Tracer = new CudaTracer();
	m_Tracer->PrepareSceneData(scene, *m_Screen);

	glutMainLoop();
	return 0;
}

void GUI::RecreateTexture(void)
{
	m_TextureWidth = rtMath::nextpow2(m_WindowWidth);
	m_TextureHeight = rtMath::nextpow2(m_WindowHeight);

	if (m_GLTexture != 0)
		glDeleteTextures(1, reinterpret_cast<GLuint*>(&m_GLTexture));

	glGenTextures(1, reinterpret_cast<GLuint*>(&m_GLTexture));
	glBindTexture(GL_TEXTURE_2D, m_GLTexture);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_TextureWidth, m_TextureHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
	glBindTexture(GL_TEXTURE_2D, 0);
}

void GUI::Display(void)
{
	m_Tracer->Render(*m_Screen);

	glClearColor(0, 0, 0, 1);
	glClear(GL_COLOR_BUFFER_BIT);

	glBindTexture(GL_TEXTURE_2D, m_GLTexture);
		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_WindowWidth, m_WindowHeight, GL_RGBA, GL_UNSIGNED_BYTE, m_Screen->GetDataPtr());

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glOrtho(0, m_WindowWidth, m_WindowHeight, 0, -1, 1);
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();

		glViewport(0, 0, m_WindowWidth, m_WindowHeight);

		glEnable(GL_TEXTURE_2D);

		const float u0 = 0.0f, u1 = static_cast<float>(m_WindowWidth) / m_TextureWidth;
		const float v0 = 0.0f, v1 = static_cast<float>(m_WindowHeight) / m_TextureHeight;

		glBegin(GL_QUADS);
			glTexCoord2f(u0, v1);
			glVertex2f(0, m_WindowHeight);
			glTexCoord2f(u1, v1);
			glVertex2f(m_WindowWidth, m_WindowHeight);
			glTexCoord2f(u1, v0);
			glVertex2f(m_WindowWidth, 0);
			glTexCoord2f(u0, v0);
			glVertex2f(0, 0);
		glEnd();
	glBindTexture(GL_TEXTURE_2D, 0);

	glutSwapBuffers();
}

void GUI::Resize(int width, int height)
{
	if (m_WindowWidth != width || m_WindowHeight != height)
	{
		m_WindowWidth = width;
		m_WindowHeight = height;

		SafeDelete(m_Screen);
		m_Screen = Image::CreateFromMemory(NULL, width, height, 32);

		this->RecreateTexture();

		m_Tracer->ResizeScreen(width, height);

		glutPostRedisplay();
	}
}

void GUI::Keyboard(uint8 key, int x, int y)
{
	bool cameraMove = true;
	rtMath::vec3 moveVec;
	SceneCamera* camera = m_Scene->GetCamera();

	switch (key)
	{
		case 'w':
			moveVec = camera->GetDirection();
			break;
		case 's':
			moveVec = -camera->GetDirection();
			break;
		case 'a':
			moveVec = -camera->GetU();
			break;
		case 'd':
			moveVec = camera->GetU();
			break;

		default:
			cameraMove = false;
	}

	if (cameraMove)
	{
		camera->SetPosition(camera->GetPosition() + moveVec);
		m_Tracer->UpdateCamera(camera);
		glutPostRedisplay();
	}
}

void GUI::Mouse(int button, int state, int x, int y)
{
	if (GLUT_LEFT_BUTTON == button)
	{
		m_MouseCaptured.x = static_cast<float>(x);
		m_MouseCaptured.y = static_cast<float>(y);
	}
}

void GUI::Motion(int x, int y)
{
	vec2 curPos(static_cast<float>(x), static_cast<float>(y));
	vec2 delta = curPos - m_MouseCaptured;
	m_MouseCaptured = curPos;

	SceneCamera* camera = m_Scene->GetCamera();
	vec3 dir = camera->GetDirection();
	vec3 up = camera->GetUpVector();
	vec3 side = rtMath::cross(up, dir);

	rtMath::RotateVector(dir, delta.x, up);
	rtMath::RotateVector(dir, delta.y, side);

	up = rtMath::cross(dir, camera->GetU());

	camera->SetDirection(dir);
	camera->SetUpVector(up);
	camera->Setup(m_WindowWidth, m_WindowHeight);
	m_Tracer->UpdateCamera(camera);

	glutPostRedisplay();
}
